# -*- coding: utf-8 -*-
# @Author: Jerry
# @Date:   2022-02-24 14:18:18
# @Last Modified by:   Jerry
# @Last Modified time: 2022-03-01 15:30:37


# **********
'''
图像梯度
目标：
  学习： - 查找图像梯度，边缘等
  使用以下函数：cv.Sobel()，cv.Scharr()，cv.Laplacian()等
理论：
  OpenCV提供三种类型的梯度滤波器或高通滤波器，即Sobel，Scharr和Laplacian

  1.Sobel 和 Scharr 算子：
  图像中的边缘区域，像素值会发生“跳跃”，对这些像素求导，在其一阶导数在边缘位置为极值，这就是Sobel算子使用的原理——极值处就是边缘。
  如果对像素值求二阶导数，会发现边缘处的导数值为0。
  Sobel算子是高斯平滑加微分运算的联合运算，因此它更抗噪声。
       可以指定要采用的导数方向，垂直或水平（分别通过参数yorder和xorder）
       还可以通过参数ksize指定内核的大小。
       如果ksize = -1，则使用3x3 Scharr滤波器，比3x3 Sobel滤波器具有更好的结果。

  2.Laplacian 算子
  Laplace函数实现的方法是先用Sobel 算子计算二阶x和y导数，再求和
  它计算了由关系Δsrc = ∂^2_src/∂x^2 + ∂^2_src/∂y^2,给出的图像的拉普拉斯图,它是每一阶导数通过Sobel算子计算。
  如果ksize=1，然后使用以下内核用于过虑
  kernel = [[0, 1, 0],
            [1,-4, 1],
            [0, 1, 0]]

  简单说，sobel是只能求的x轴或y轴上的边缘，而laplacian能同时计算的x、y轴的边缘
  在区边缘钱如果能先用各种滤波器做降噪处理，就能求得比原图直接求边缘较好的效果

  比如车牌的追踪和识别，很重要的内容就是识别边缘，一辆汽车经过摄像头，必须能够识别到车牌区域，能够识别到里边的文字和字符
'''

# **********
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
import os
rootpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
datapath = os.path.join(rootpath,'data')
imgpath = lambda name: os.path.join(datapath,name)


# **********
'''
下面的代码显示了单个图表中的所有算子。
所有内核都是5x5大小。
输出图像的深度通过-1得到结果的np.uint8型。

cv.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]]) -> dst

'''
def Laplacian_sobel_test():
    img = cv.imread(imgpath('sudoku.png'),cv.COLOR_BGR2GRAY)
    img = cv.cvtColor(img,cv.COLOR_BGR2GRAY)

    laplacian = cv.Laplacian(img,cv.CV_64F)
    sobelx = cv.Sobel(img,cv.CV_64F,1,0,ksize=5) # sobel一种边缘检测滤波器, 检测x轴方向的颜色变化之处
    sobely = cv.Sobel(img,cv.CV_64F,0,1,ksize=5) # sobel一种边缘检测滤波器, 检测y轴方向的颜色变化之处

    plt.subplot(221),plt.imshow(img,cmap='gray'),plt.title('Original')
    plt.xticks([]),plt.yticks([]),

    plt.subplot(222),plt.imshow(laplacian,cmap='gray'),plt.title('Laplacian')
    plt.xticks([]),plt.yticks([]),

    plt.subplot(223),plt.imshow(sobelx,cmap='gray'),plt.title('Sobelx')
    plt.xticks([]),plt.yticks([]),

    plt.subplot(224),plt.imshow(sobely,cmap='gray'),plt.title('Sobely')
    plt.xticks([]),plt.yticks([]),

    plt.show()


# **********
'''
在我们的最后一个示例中，输出数据类型为cv.CV_8U或np.uint8。
但这有一个小问题:
黑色到白色的过渡被视为正斜率（具有正值），而白色到黑色的过渡被视为负斜率（具有负值）。

因此，当您将数据转换为np.uint8时，所有负斜率均设为零。简而言之，您会错过这一边缘信息。

如果要检测两个边缘，
更好的选择是将输出数据类型保留为更高的形式，例如cv.CV_16S，cv.CV_64F等，
取其绝对值，
然后转换回cv.CV_8U。

下面的代码演示了用于水平Sobel滤波器和结果差异的此过程。
'''
def data_bype():
    img = cv.cvtColor(cv.imread(imgpath('box_black_white.png')),cv.COLOR_BGR2GRAY)
    sobelx8u = cv.Sobel(img,cv.CV_8U,1,0,ksize=5) # sobel一种边缘检测滤波器, 检测x轴方向的颜色变化之处
    sobel64f = cv.Sobel(img,cv.CV_64F,1,0,ksize=5)
    abs_sobel64f = np.absolute(sobel64f)
    sobel_8u = np.uint8(abs_sobel64f)

    plt.subplot(1,4,1),plt.imshow(img,cmap = 'gray')
    plt.title('Original'), plt.xticks([]), plt.yticks([])

    plt.subplot(1,4,2),plt.imshow(sobelx8u,cmap = 'gray')
    plt.title('Sobel CV_8U'), plt.xticks([]), plt.yticks([])

    plt.subplot(1,4,3),plt.imshow(sobel_8u,cmap = 'gray')
    plt.title('Sobel abs(CV_64F)'), plt.xticks([]), plt.yticks([])

    M = cv.getStructuringElement(cv.MORPH_RECT,(5,5))
    sobel_8u = cv.morphologyEx(sobel_8u,cv.MORPH_CLOSE,M)
    plt.subplot(1,4,4),plt.imshow(sobel_8u,cmap = 'gray')
    plt.title('Sobel abs(CV_64F 1)'), plt.xticks([]), plt.yticks([])

    plt.show()




if __name__ == '__main__':
    # Laplacian_sobel_test()
    data_bype()




